package utils

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"mime"
	"net/smtp"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	boundary     = "Boundary"
	linkBreak    = "\r\n"
	mineBoundary = linkBreak + "--" + boundary + linkBreak
)

type SMTPConf struct {
	SmtpHost     string `protobuf:"bytes,1,opt,name=smtp_host,json=smtpHost,proto3" json:"smtp_host,omitempty"`             // smtp地址
	SmtpPort     uint32 `protobuf:"varint,2,opt,name=smtp_port,json=smtpPort,proto3" json:"smtp_port,omitempty"`            // smtp端口
	SmtpUser     string `protobuf:"bytes,3,opt,name=smtp_user,json=smtpUser,proto3" json:"smtp_user,omitempty"`             // smtp用户名
	SmtpPassword string `protobuf:"bytes,4,opt,name=smtp_password,json=smtpPassword,proto3" json:"smtp_password,omitempty"` // smtp密码
	SmtpSsl      bool   `protobuf:"varint,5,opt,name=smtp_ssl,json=smtpSsl,proto3" json:"smtp_ssl,omitempty"`               // smtp是否使用ssl
}

type Message struct {
	To          []string
	Cc          []string
	Subject     string
	Msg         string
	Attachments []string
}

func (m *Message) Sned(c *SMTPConf) error {
	buffer := bytes.NewBuffer(nil)
	buffer.WriteString(m.header(c))
	buffer.WriteString(m.body())
	for _, attachment := range m.Attachments {
		if err := m.writeFile(buffer, attachment); err != nil {
			return err
		}
	}
	addr := fmt.Sprintf("%s:%d", c.SmtpHost, c.SmtpPort)
	auth := LoginAuth(c.SmtpUser, c.SmtpPassword)
	return smtp.SendMail(addr, auth, c.SmtpUser, MergeSlice(m.To, m.Cc), buffer.Bytes())
}

func (m *Message) header(c *SMTPConf) string {
	Header := map[string]string{
		"From":         c.SmtpUser,
		"To":           strings.Join(m.To, ";"),
		"Cc":           strings.Join(m.Cc, ";"),
		"Subject":      m.Subject,
		"Content-Type": "multipart/mixed;boundary=" + boundary,
		"Mime-Version": "1.0",
		"Date":         time.Now().String(),
	}
	header := ""
	for key, value := range Header {
		header += key + ":" + value + linkBreak
	}
	return header + linkBreak
}

func (m *Message) body() string {
	ct := "Content-Type:text/plain;charset=utf-8"
	return mineBoundary + ct + linkBreak + linkBreak + m.Msg + linkBreak
}

func (m *Message) writeFile(buffer *bytes.Buffer, filePath string) error {
	fileName := filepath.Base(filePath)
	attachment := linkBreak + "--" + boundary + linkBreak
	attachment += "Content-Transfer-Encoding:base64" + linkBreak
	attachment += "Content-Disposition:attachment" + linkBreak
	attachment += "Content-Type:application/octet-stream;name=\"" + mime.BEncoding.Encode("UTF-8", fileName) + "\""
	attachment += linkBreak
	buffer.WriteString(attachment)
	file, err := os.ReadFile(filePath)
	if err != nil {
		return err
	}
	payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
	base64.StdEncoding.Encode(payload, file)
	buffer.WriteString(linkBreak)
	for index, line := 0, len(payload); index < line; index++ {
		buffer.WriteByte(payload[index])
		if (index+1)%76 == 0 {
			buffer.WriteString(linkBreak)
		}
	}
	return nil
}

func MergeSlice(s1 []string, s2 []string) []string {
	slice := make([]string, len(s1)+len(s2))
	copy(slice, s1)
	copy(slice[len(s1):], s2)
	return slice
}

type loginAuth struct {
	username, password string
}

func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	// return "LOGIN", []byte{}, nil
	return "LOGIN", []byte(a.username), nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		}
	}
	return nil, nil
}
